package com.imooc.feeds.service;

import cn.hutool.core.bean.BeanUtil;
import com.imooc.commons.constant.ApiConstant;
import com.imooc.commons.constant.RedisKeyConstant;
import com.imooc.commons.exception.ParameterException;
import com.imooc.commons.model.domain.ResultInfo;
import com.imooc.commons.model.pojo.Feeds;
import com.imooc.commons.model.vo.FeedsVO;
import com.imooc.commons.model.vo.ShortDinerInfo;
import com.imooc.commons.model.vo.SignInDinerInfo;
import com.imooc.commons.utils.AssertUtil;
import com.imooc.feeds.mapper.FeedsMapper;
import org.bouncycastle.asn1.cms.KEKIdentifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.net.URL;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author gcq
 * @Create 2022-11-14
 */
@Service
public class FeedsService {

    @Value("${service.name.ms-oauth-server}")
    private String oauthServerName;

    @Value("${service.name.ms-follow-server}")
    private String followServerName;

    @Value("${service.name.ms-diners-server}")
    private String dinersServerName;

    @Resource
    private RestTemplate restTemplate;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private FeedsMapper feedsMapper;

    /**
     * 根据时间由近到远，每次查询 20 条 Feed
     *
     * @param page
     * @param accessToken
     * @return
     */
    public List<FeedsVO> selectForPage(Integer page, String accessToken){
        if(page == null){
            page = 1;
        }
        // 获取登录信息
        SignInDinerInfo dinerInfo = loadSignInDinerInfo(accessToken);

        // 我关注好友的 feedsKey
        String key = RedisKeyConstant.following_feeds.getKey() + dinerInfo.getId();

        // SortedSet 的 ZREVRANGE 命令是闭区间
        long start = (page - 1) * ApiConstant.PAGE_SIZE;
        long end = page * ApiConstant.PAGE_SIZE - 1;
        Set<Integer> feedIds = redisTemplate.opsForZSet().reverseRange(key, start, end);

        // 根据多个主键查询 feeds
        List<Feeds> feeds = feedsMapper.findFeedsByIds(feedIds);
        // 初始化关注好友 ID 集合
        List<Integer> followingDinersIds = new ArrayList<>();
        // 添加用户 ID 至集合，同时将 feeds 转换成 VO
        List<FeedsVO> feedsVOS = feeds.stream()
                .map(feed -> {
                    FeedsVO feedsVO = new FeedsVO();
                    BeanUtil.copyProperties(feed, feedsVO);
                    followingDinersIds.add(feed.getFkDinerId());
                    return feedsVO;
                })
                .collect(Collectors.toList());

        // 远程调用接口，获取 Feed 用户信息
        ResultInfo resultInfo = restTemplate.getForObject(dinersServerName + "findByIds?access_token=${accessToken}&ids=${ids}",
                ResultInfo.class, accessToken, followingDinersIds);
        if(resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {
            throw new ParameterException(resultInfo.getCode(), resultInfo.getMessage());
        }
        List<LinkedHashMap> dinerInfoMaps = (ArrayList) resultInfo.getData();
        // KEY dinerId value dinerInfo
        Map<Integer, ShortDinerInfo> dinerInfoMap = dinerInfoMaps.stream()
                .collect(Collectors.toMap(
                        diner -> (Integer) diner.get("id"),
                        diner -> BeanUtil.fillBeanWithMap(diner, new ShortDinerInfo(), true)
                ));
        // 返回VO,从 map 中根据 id 中获取对象
        feedsVOS.forEach(feed -> {
            feed.setDinerInfo(dinerInfoMap.get(feed.getFkDinerId()));
        });
        return feedsVOS;
    }

    /**
     * 变更 Feed
     * A关注了
     *
     * @param followingDinerId 关注的好友的 ID
     * @param accessToken      访问token
     * @param type              1 关注 0 取关
     */
    @Transactional(rollbackFor = Exception.class)
    public void addFollowingFeed(Integer followingDinerId, String accessToken, int type){
        // 请选择关注的好友
        AssertUtil.isTrue(followingDinerId == null || followingDinerId < 1, "请选择关注的好友");
        // 获取登录用户信息
        SignInDinerInfo dinerInfo = loadSignInDinerInfo(accessToken);

        // 获取关注/取关食客所有的Feed
        // 获取关注好友Id 的所有feeds内容
        List<Feeds> feedsList = feedsMapper.findByDinerId(followingDinerId);
        String key = RedisKeyConstant.following_feeds.getKey() + dinerInfo.getId();
        // 取关则删除缓存中feeds数据
        // 关注则添加feeds

        // 取关
        if(type == 0){
            List<Integer> feedsIds = feedsList.stream()
                    .map(feed -> feed.getId())
                    .collect(Collectors.toList());
            // 移除 feeds
            redisTemplate.opsForZSet().remove(key, feedsIds.toArray(new Integer[]{}));
        } else if(type == 1){
            // 关注
            Set<DefaultTypedTuple> typedTuple = feedsList.stream()
                    .map(feed -> new DefaultTypedTuple(feed.getId(), (double) feed.getUpdateDate().getTime()))
                    .collect(Collectors.toSet());
            // 添加 feeds
            redisTemplate.opsForZSet().add(key,typedTuple);
        }
    }


    @Transactional(rollbackFor = Exception.class)
    public void delete(Integer id, String accessToken){
        // 选择需要删除的 feed
        AssertUtil.isTrue(id == null || id < 1, "请选择需要删除的Feed");
        
        // 获取登录用户
        SignInDinerInfo dinerInfo = loadSignInDinerInfo(accessToken);

        // 获取 Feed 内容
        Feeds feeds = feedsMapper.findById(id);
        AssertUtil.isTrue(feeds == null, "该Feed已被删除");

        // 判断 Feed 是否已被删除且只能删除自己的 Feed
        AssertUtil.isTrue(!feeds.getFkDinerId().equals(dinerInfo.getId()), "只能删除自己的Feeds");
        
        // 删除
        feedsMapper.delete(id);

        // 将内容从粉丝列表中移除 -- 异步消息1
        // 先获取我的粉丝
        List<Integer> followers = findFollowers(dinerInfo.getId());
        followers.forEach(follower -> {
            String key = RedisKeyConstant.following_feeds.getKey() + follower;
            redisTemplate.opsForZSet().remove(key, feeds.getId());
        });
    }

    /**
     * 添加Feed
     *
     * 1、获取登录用户粉丝集合
     *      登录用户的粉丝集合可以查看 follow 模块。
     *      关注的人：set 结构
     *      我的粉丝： set 结构
     *      我关注了谁？ 谁关注了我
     * 2、向粉丝集合推送 feed 流
     *
     *
     * @param feeds
     * @param accessToken
     */
    public void create(Feeds feeds, String accessToken){
        // 效验Feed内容不能太长
        AssertUtil.isNotEmpty(feeds.getContent(), "内容不能为空");
        AssertUtil.isTrue(feeds.getContent().length() > 255, "内容太长");

        // 获取登录用户信息
        SignInDinerInfo dinerInfo = loadSignInDinerInfo(accessToken);

        // Feed 登录用户关联信息
        feeds.setFkDinerId(dinerInfo.getId());
        feeds.setFkRestaurantId(1);

        // 添加Feed
        int count = feedsMapper.save(feeds);
        AssertUtil.isTrue(count == 0, "添加失败");

        // 推送到粉丝的列表中，后续这里应该使用消息队列解决性能问题
        List<Integer> followers = findFollowers(dinerInfo.getId());
        Long now = System.currentTimeMillis();
        followers.forEach(follower ->{
            // 将 feed_Id 写入到缓存中，然后取出 feed_id 获取详细的内容/或者直接将feed内容存储到redis中
            // key = feeds + 粉丝Id
            String key = RedisKeyConstant.following_feeds.getKey() + follower;
            redisTemplate.opsForZSet().add(key, feeds.getId(), now);
        });
    }

    /**
     * 获取粉丝Id集合
     *
     * @param dinerId
     * @return 粉丝Id
     */
    private List<Integer> findFollowers(Integer dinerId) {
        String url = followServerName + "followers/" + dinerId;
        ResultInfo resultInfo = restTemplate.getForObject(url, ResultInfo.class);
        if(resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {
            throw new ParameterException(resultInfo.getMessage());
        }
            List<Integer> followers = (List<Integer>) resultInfo.getData();
        return followers;
    }

    /**
     * 获取登录用户信息
     *
     * @param accessToken 访问token
     * @return 登录用户信息
     */
    private SignInDinerInfo loadSignInDinerInfo(String accessToken) {
        AssertUtil.mustLogin(accessToken);

        String url = oauthServerName + "/user/me?access_token={accessToken}";
        ResultInfo resultInfo = restTemplate.getForObject(url, ResultInfo.class, accessToken);
        if (resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {
            throw new ParameterException(resultInfo.getMessage());
        }
        SignInDinerInfo dinerInfo = BeanUtil.fillBeanWithMap((LinkedHashMap) resultInfo.getData(), new SignInDinerInfo(), false);
        return dinerInfo;
    }
}